package edu.gatech.oad.mule;

import java.awt.Dimension;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

import edu.gatech.oad.mule.config.interfaces._ConfigurationPanel;
import edu.gatech.oad.mule.view.interfaces.RenderableTile;
import edu.gatech.oad.mule.view.interfaces.RenderableWorldMap;

/**
 * This map represents the world map with the plots of land
 *
 * @author Brendan McGarry, Dale Avery, D'Miria Collins, Natasha Parekh, Jaemin Park
 * @version %I%, %G%
 */
public class WorldMap extends Map implements RenderableWorldMap{

	private static final long serialVersionUID = -2428213201676999223L;

	//standard map representation
	public static final char[][] STANDARD_MAP = 
		{{Tile.PLAINS, Tile.PLAINS, Tile.MTN1, Tile.PLAINS, Tile.RIVER, Tile.PLAINS, Tile.MTN3, Tile.PLAINS, Tile.PLAINS}, 
		{Tile.PLAINS, Tile.MTN1, Tile.PLAINS, Tile.PLAINS, Tile.RIVER, Tile.PLAINS, Tile.PLAINS, Tile.PLAINS, Tile.MTN3}, 
		{Tile.MTN3, Tile.PLAINS, Tile.PLAINS, Tile.PLAINS, Tile.TOWN, Tile.PLAINS, Tile.PLAINS, Tile.PLAINS, Tile.MTN1}, 
		{Tile.PLAINS, Tile.MTN2, Tile.PLAINS, Tile.PLAINS, Tile.RIVER, Tile.PLAINS, Tile.MTN2, Tile.PLAINS, Tile.PLAINS}, 
		{Tile.PLAINS, Tile.PLAINS, Tile.MTN2, Tile.PLAINS, Tile.RIVER, Tile.PLAINS, Tile.PLAINS, Tile.PLAINS, Tile.MTN2}};

	//number of tiles wide and tall
	private int width = STANDARD_MAP[0].length;
	private int height = STANDARD_MAP.length;
	
	//number of pixels wide and tall
	public static final int WIDTH = STANDARD_MAP[0].length * Tile.WIDTH;
	public static final int HEIGHT = STANDARD_MAP.length * Tile.HEIGHT;
	
	//for random stuffs
	private static Random r = new Random();
	
	//crystite concentrations
	private final int CRYSTITE_HIGH = 3;
	private final int CRYSTITE_MEDIUM = 2;
	private final int CRYSTITE_LOW = 1;

	/**
	 * This constructor sets up a world map based on the
	 * type of map selected in the configuration GUI
	 *
	 * @param p A reference to the GUI for configuring the map type
	 */
	public WorldMap(_ConfigurationPanel p){
		System.out.println("worldMap");
		System.out.println("w: " + WIDTH + "; h: " + HEIGHT);
		System.out.println(width + "x" + height + " tiles");
		zoneGrid = new Tile[width*height];
		if(p.getMap() == "Standard"){
			configureStandardMap();
		}//end if
		else if(p.getMap() == "Random"){
			configureRandomMap();
		}//end else if
		
		distributeCrystite(r.nextInt(zoneGrid.length));
		
		System.out.println(toString());

		System.out.println("/worldMap");		
	}//end constructor
	
	/**
	 * Sets up the world map by arranging the tiles
	 * in a random fashion
	 */
	private void configureRandomMap(){
		//location of the mountain plots in this row
		int m1Loc = 0;
		int m2Loc = 0;
		//what type of mountain is it?  Since each row has a total value
		//  of 4 mountains, we find out the value of the first mountain
		//  tile then do 4-m1Val to get the second one
		int m1Val = 0;
		//each row (height)
		for(int i = 0; i < height; i++){
		    //see what type of mountain this will be	
			m1Val = r.nextInt(2) + 1;
			//recalculate the location for these mountains
			m1Loc = r.nextInt(width);
			m2Loc = r.nextInt(width);
			//since the middle value is reserved for river plots or the town
			//  we keep generating a value we are not the middle of this row
			while(m1Loc == width/2){
				m1Loc = r.nextInt(width);
			}//end while
			//we need to keep generating random values until we aren't in the 
			//  middle of the row and we can't be the same as m1's location
			while(m2Loc == width/2 || m2Loc == m1Loc){
				m2Loc = r.nextInt(width);
			}//end while
			System.out.println("m1: " + m1Loc + ", m2: " + m2Loc);
			//each column (WIDTH)
			for(int j = 0; j < width; j++){
				System.out.println(i*width+j + ": " + "(" + i + ", " + j + ")");
				//m1
				if(j == m1Loc){
					zoneGrid[i*width+j] = new Tile(j, i, ((char)(m1Val + 48)));
				}//end if
				//m2
				else if(j == m2Loc){
					zoneGrid[i*width+j] = new Tile(j, i, ((char)(4 - m1Val + 48)));
				}//end if
				//we are in the middle of the board.  the town goes here
				else if(j == width/2 && i == height/2){
					zoneGrid[i*width+j] = new Tile(j, i, Tile.TOWN);
				}//end else if
				//we are in the middle of the row but not the middle of the board, river goes here
				else if(j == width/2){
					zoneGrid[i*width+j] = new Tile(j, i, Tile.RIVER);
				}//end else if
				//plains goes everywhere else
				else{
					zoneGrid[i*width+j] = new Tile(j, i, Tile.PLAINS);
				}//end else
			}//end inner for
		}//end outer for
	}//end method configureRandomMap

	/**
	 * Sets up the world map by arranging the tiles in
	 * a predetermined fashion
	 */
	private void configureStandardMap(){
		//each row of the map (height)
		for(int i = 0; i < height; i++){
			//each col of the map (WIDTH)
			for(int j = 0; j < width; j++){
				//create a tile at this location
				zoneGrid[i*width+j] = new Tile(j, i, STANDARD_MAP[i][j]);
			}//end inner for
		}//end outer for
	}//end method configureStandardMap

	/**
	 * Places crystite on the map in a diamond pattern.  The center
	 * has the greatest concentration and it spirals outward.
	 * 
	 * H = High concentration
	 * M = Medium concentration
	 * L = Low concentration
	 * 
	 *     L
	 *   L M L
	 * L M H M L
	 *   L M L
	 *     L
	 *     
	 * @param crystiteCenter The center of the crystite meteor crash.
	 * This is where the crystite is most concentrated
	 */
	private void distributeCrystite(int crystiteCenter){
		
		Point center = zoneToRowCol(crystiteCenter);
		int centerX = ((int)center.getX());
		int centerY = ((int)center.getY());
		System.out.println("crystite center: [" + crystiteCenter + "] = " + center);
		
		//high
		((Tile)zoneGrid[crystiteCenter]).setCrystite(CRYSTITE_HIGH);
		
		//medium
		if(rowColToZone(centerX + 1, centerY) > 0){
			((Tile)zoneGrid[rowColToZone(centerX + 1, centerY)]).setCrystite(CRYSTITE_MEDIUM);
		}//end if
		if(rowColToZone(centerX - 1, centerY) > 0){
			((Tile)zoneGrid[rowColToZone(centerX - 1, centerY)]).setCrystite(CRYSTITE_MEDIUM);
		}//end if
		if(rowColToZone(centerX, centerY + 1) > 0){
			((Tile)zoneGrid[rowColToZone(centerX, centerY + 1)]).setCrystite(CRYSTITE_MEDIUM);
		}//end if
		if(rowColToZone(centerX, centerY - 1) > 0){
			((Tile)zoneGrid[rowColToZone(centerX, centerY - 1)]).setCrystite(CRYSTITE_MEDIUM);
		}//end if
		
		//low
		if(rowColToZone(centerX + 2, centerY) > 0){
			((Tile)zoneGrid[rowColToZone(centerX + 2, centerY)]).setCrystite(CRYSTITE_LOW);
		}//end if
		if(rowColToZone(centerX - 2, centerY) > 0){
			((Tile)zoneGrid[rowColToZone(centerX - 2, centerY)]).setCrystite(CRYSTITE_LOW);
		}//end if
		if(rowColToZone(centerX, centerY + 2) > 0){
			((Tile)zoneGrid[rowColToZone(centerX, centerY + 2)]).setCrystite(CRYSTITE_LOW);
		}//end if
		if(rowColToZone(centerX, centerY - 2) > 0){
			((Tile)zoneGrid[rowColToZone(centerX, centerY - 2)]).setCrystite(CRYSTITE_LOW);
		}//end if
		if(rowColToZone(centerX + 1, centerY + 1) > 0){
			((Tile)zoneGrid[rowColToZone(centerX + 1, centerY + 1)]).setCrystite(CRYSTITE_LOW);
		}//end if
		if(rowColToZone(centerX - 1, centerY + 1) > 0){
			((Tile)zoneGrid[rowColToZone(centerX - 1, centerY + 1)]).setCrystite(CRYSTITE_LOW);
		}//end if
		if(rowColToZone(centerX + 1, centerY - 1) > 0){
			((Tile)zoneGrid[rowColToZone(centerX + 1, centerY - 1)]).setCrystite(CRYSTITE_LOW);
		}//end if
		if(rowColToZone(centerX - 1, centerY - 1) > 0){
			((Tile)zoneGrid[rowColToZone(centerX - 1, centerY - 1)]).setCrystite(CRYSTITE_LOW);
		}//end if
		
	}//end method distributeCrystite
	
	@Override
	public Zone getCollidedZone(Player p){
		//int col = p.getX() / Tile.WIDTH;
		//int row = p.getY() / Tile.HEIGHT;
		return (zoneGrid[(p.getY() / Tile.HEIGHT) * width + (p.getX() / Tile.WIDTH)]);
	}//end method getCollidedZone

	/**
	 * This method calculates the production of a player's tiles
	 * and then adds the resources to the player's wallet
	 * 
	 * @param p The player to calculate production for
	 */
	private void production(Player p){
		if(p != null){
			//don't even bother if the player owns nothing
			if(p.getNumOwnedTiles() > 0){
				//ArrayList of all the tiles the player owns with
				//  outfitted MULEs on them
				ArrayList<Tile> tiles = new ArrayList<Tile>();
				//loop through each zone on the map
				for(Zone z : zoneGrid){
					//cast zone to tile
					Tile t = (Tile)z;
					//current tile is owned by the player and it
					//  has an outfitted MULE on it
					if(t.getOwner() != null && t.getOwner().equals(p) && t.hasMULE()){
						tiles.add(t);
					}//end if
				}//end for each
				System.out.print(p.getName() + " has [" + p.getEnergy() + "] energy for [" + tiles.size() + "] plots. ");
				//make sure the player has plots with MULEs on it to harvest
				if(tiles.size() > 0){
					//index; points to the current tile in arraylist tiles
					int i = 0;
					//this player has more tiles than energy
					if(tiles.size() > p.getEnergy()){
						System.out.print(p.getName() + " has an energy shortage.  Distributing energy to random tiles. ");
						//randomize the tiles
						Collections.shuffle(tiles);
					}//end if
					System.out.print("Harvesting... ");
					//repeat until the player has run out of energy
					while(p.getEnergy() > 0){
						//energize the tile
						tiles.get(i).setEnergized(true);
						//harvest resources
						tiles.get(i).produce();
						//subtract energy from player
						p.addEnergy(-1);
						//increment index pointer
						i++;
						//we still have energy remaining, but we've
						//  looped through all of the tiles the player
						//  owns
						if(i == tiles.size()){
							break;
						}//end if
					}//end while
					System.out.println("Harvest complete.");
				}//end if
				else{
					System.out.println(p.getName() + " owns plots, but has not placed any MULEs to harvest the resources.");
				}//end else
			}//end if
			else{
				System.out.println(p.getName() + " owns nothing.");
			}//end else
		}//end if
		else{
			System.out.println("passed in null for player; cannot calculate production");
		}//end else
	}//end method production
	
	/**
	 * Calculates the production of each player's plots and adds the
	 * resources produced to the respective player's wallet
	 * 
	 * @param players Array of players to calculate production for
	 */
	public void production(Player[] players){
		if(players != null && players.length != 0){
			//reset all the tiles on the map to not be energized
			resetEnergized();
			//calculate the production for each player
			for(Player p : players){
				System.out.print("calculating production for: " + p.getName() + ". ");
				production(p);
			}//end for each
		}//end if
		else{
			System.out.println("parameter players[] is null/empty; cannot calculate production");
		}//end else
	}//end method production
	
	/**
	 * Resets all the tiles in the map to not be energized
	 */
	public void resetEnergized(){
		for(Zone z : zoneGrid){
			((Tile)z).setEnergized(false);
		}//end for each
	}//end method resetEnergized
	
	@Override
	public String toString(){
		String s = "";
		for(int i = 0; i < height; i++){
			for(int j = 0; j < width; j++){
				s += (zoneGrid[i*width+j].toString() + " ");
			}//end for
			s += "\n";
		}//end for
		return s;
	}//end method toString
	
	/**
	 * Converts a (row, column) pair to an integer in zoneGrid
	 * 
	 * @param row The row the tile is located (starting with 0)
	 * @param col The column the tile is located (starting with 0)
	 * @return An integer that corresponds to this tile's location in
	 * zoneGrid; This will be -1 if it does not exist
	 */
	private int rowColToZone(int row, int col){
		//check row in bounds
		if(row >= height || row < 0){
			return -1;
		}//end if
		//check col in bounds
		if(col >= width || col < 0){
			return -1;
		}//end if
		return (row * width + col);
	}//end method translate
	
	/**
	 * Converts a zoneGrid index to a (row, column) pair in the map
	 * 
	 * @param zoneIndex The index in 
	 * @return (row, column) pair that corresponds to the tile's location
	 * in the map.  This will be null if it doesn't exist
	 */
	private Point zoneToRowCol(int zoneIndex){
		if((zoneIndex < 0) || (zoneIndex >= (width * height))){
			return null;
		}//end if
		return (new Point((zoneIndex / width), (zoneIndex % width)));
	}//end method zoneToRowCol

	//==============================
	//	RenderableWorldMap
	//==============================
	
	@Override
	public MapType getMapType(){
		return MapType.WORLD;
	}//end method getMapType

	@Override
	public Zone[] getZones(){
		return zoneGrid;
	}//end method getZones

	@Override
	public Dimension getSize() {
		return new Dimension(this.WIDTH, this.HEIGHT);
	}//end method getSize

	@Override
	public RenderableTile[] getTiles() {
		return (RenderableTile[]) zoneGrid;
	}//end method getTiles

}//end class WorldMap
